<?php

namespace app\common\library;

use app\common\model\Attachment;
use jueqi\QcloudCos;
use jueqi\QcloudOss;
use jueqi\Random;
use think\Exception;
use think\exception\FileException;
use think\facade\Config;
use think\File;
use think\file\UploadedFile;

/**
 * 上传
 */
class Upload
{
    /**
     * 配置信息
     * @var array
     */
    protected $config = [];

    /**
     * @var UploadedFile
     */
    protected $file = null;

    /**
     * 是否是图片
     * @var bool
     */
    protected $isImage = false;

    /**
     * 文件信息
     * @var null
     */
    protected $fileInfo = null;

    /**
     * 细目
     * @var string
     */
    protected $topic = 'default';

    /**
     * 存储方式
     * @var string
     */
    protected $storage = "local";

    /**
     * 构造方法
     * @param UploadedFile|null $file
     * @param array $config
     * @throws Exception
     */
    public function __construct(UploadedFile $file = null, array $config = [])
    {
        $this->config = Config::get('upload');
        if ($config) {
            $this->config  = array_merge($this->config, $config);
            $this->topic   = $this->config['topic'];
            $this->storage = $this->config['storage'];
        }

        if ($file) {
            $this->setFile($file);
        }
    }

    /**
     * 设置文件
     * @param UploadedFile $file
     * @throws Exception
     */
    public function setFile(UploadedFile $file)
    {
        if (empty($file)) {
            throw new Exception('没有文件被上传！', 10001);
        }

        $suffix             = strtolower($file->extension());
        $suffix             = $suffix && preg_match("/^[a-zA-Z0-9]+$/", $suffix) ? $suffix : 'file';
        $fileInfo['suffix'] = $suffix;
        $fileInfo['type']   = $file->getOriginalMime();
        $fileInfo['size']   = $file->getSize();
        $fileInfo['name']   = $file->getOriginalName();
        $fileInfo['sha1']   = $file->sha1();

        $this->file     = $file;
        $this->fileInfo = $fileInfo;
    }

    /**
     * 上传的文件是否为图片
     * @return bool
     */
    public function isImage(): bool
    {
        return $this->isImage;
    }

    /**
     * 获取文件后缀
     * @return mixed|string
     */
    public function getSuffix()
    {
        return $this->fileInfo['suffix'] ?: 'file';
    }

    /**
     * 上传文件
     * @param string|null $saveName // 保存文件名(保存路径+文件名)
     * @param int $adminId // 上传管理员ID
     * @param int $userId // 上传用户ID
     * @return array
     * @throws Exception
     */
    public function upload(string $saveName = null, int $adminId = 0, int $userId = 0): array
    {
        if (empty($this->file)) {
            throw new Exception('没有文件被上传或文件大小超出服务器上传限制！');
        }

        $this->checkSize();
        $this->checkMimetype();
        $this->checkIsImage();

        $params = [
            'topic'     => $this->topic,
            'admin_id'  => $adminId,
            'user_id'   => $userId,
            'url'       => $this->getSaveName($saveName),
            'width'     => $this->fileInfo['width'] ?? 0,
            'height'    => $this->fileInfo['height'] ?? 0,
            'name'      => substr(htmlspecialchars(strip_tags($this->fileInfo['name'])), 0, 100),
            'size'      => $this->fileInfo['size'],
            'mime_type' => $this->fileInfo['type'],
            'storage'   => 'local',
            'sha1'      => $this->fileInfo['sha1']
        ];

        try {
            $attachment = new Attachment();
            $attachment->data(array_filter($params));
            $res = $attachment->save();
            if (!$res) {
                $attachment = Attachment::where([
                    ['sha1', '=', $params['sha1']],
                    ['topic', '=', $params['topic']],
                    ['storage', '=', $params['storage']],
                ])->find();
            } else {
                if ($this->storage === 'cos') {
                    // 腾讯云上传
                    $this->cosUpload();
                } else if ($this->storage === 'oss') {
                    // 阿里云上传
                    $this->ossUpload();
                }else{
                    // 本地上传
                    $this->move($saveName);
                }
            }
            return [
                'code' => 1,
                'data' => $attachment->toArray(),
                'msg'  => '',
            ];
        }catch (Exception | FileException $e){
            return [
                'code' => 2,
                'data' => [],
                'msg'  => $e->getMessage(),
            ];
        }
    }

    /**
     * 检查文件大小
     * @throws Exception
     */
    protected function checkSize()
    {
        $size = file_unit_to_byte($this->config['maxsize']);
        if ($this->fileInfo['size'] > $size) {
            throw new Exception(__('The uploaded file is too large (%sMiB), Maximum file size:%sMiB', [
                round($this->fileInfo['size'] / pow(1024, 2), 2),
                round($size / pow(1024, 2), 2)
            ]));
        }
    }

    /**
     * 检查文件类型
     * @return bool
     * @throws Exception
     */
    protected function checkMimetype(): bool
    {
        $mimetypeArr = explode(',', strtolower($this->config['mimetype']));
        $typeArr     = explode('/', $this->fileInfo['type']);
        //验证文件后缀
        if ($this->config['mimetype'] === '*'
            || in_array($this->fileInfo['suffix'], $mimetypeArr) || in_array('.' . $this->fileInfo['suffix'], $mimetypeArr)
            || in_array($this->fileInfo['type'], $mimetypeArr) || in_array($typeArr[0] . "/*", $mimetypeArr)) {
            return true;
        }
        throw new Exception(__('The uploaded file format is not allowed'), 10002);
    }

    /**
     * 是否是图片并设置好相关属性
     * @return bool
     * @throws Exception
     */
    protected function checkIsImage(): bool
    {
        if (in_array($this->fileInfo['type'], ['image/gif', 'image/jpg', 'image/jpeg', 'image/bmp', 'image/png', 'image/webp']) || in_array($this->fileInfo['suffix'], ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'webp'])) {
            $imgInfo = getimagesize($this->file->getPathname());
            if (!$imgInfo || !isset($imgInfo[0]) || !isset($imgInfo[1])) {
                throw new Exception(__('The uploaded image file is not a valid image'));
            }
            $this->fileInfo['width']  = $imgInfo[0];
            $this->fileInfo['height'] = $imgInfo[1];
            $this->isImage            = true;
            return true;
        }
        return false;
    }

    /**
     * 获取文件保存名
     * @param string|null $saveName
     * @param string|null $filename
     * @param string|null $sha1
     * @return array|mixed|string|string[]
     */
    public function getSaveName(string $saveName = null, string $filename = null, string $sha1 = null)
    {
        if ($filename) {
            $suffix = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
            $suffix = $suffix && preg_match("/^[a-zA-Z0-9]+$/", $suffix) ? $suffix : 'file';
        } else {
            $suffix = $this->fileInfo['suffix'];
        }
        $filename   = $filename ?: ($suffix ? substr($this->fileInfo['name'], 0, strripos($this->fileInfo['name'], '.')) : $this->fileInfo['name']);
        $sha1       = $sha1 ?: $this->fileInfo['sha1'];
        $replaceArr = [
            '{topic}'    => $this->topic,
            '{year}'     => date("Y"),
            '{mon}'      => date("m"),
            '{day}'      => date("d"),
            '{hour}'     => date("H"),
            '{min}'      => date("i"),
            '{sec}'      => date("s"),
            '{random}'   => Random::build(),
            '{random32}' => Random::build('alnum', 32),
            '{filename}' => substr($filename, 0, 100),
            '{suffix}'   => $suffix,
            '{.suffix}'  => $suffix ? '.' . $suffix : '',
            '{filesha1}' => $sha1,
        ];
        $saveName   = $saveName ?: $this->config['savename'];
        return str_replace(array_keys($replaceArr), array_values($replaceArr), $saveName);
    }

    /**
     * 上传文件到本地
     * @param $saveName
     * @return File
     */
    public function move($saveName = null): File
    {
        $saveName  = $saveName ?: $this->getSaveName();
        $saveName  = '/' . ltrim($saveName, '/');
        $uploadDir = substr($saveName, 0, strripos($saveName, '/') + 1);
        $fileName  = substr($saveName, strripos($saveName, '/') + 1);
        $destDir   = root_path() . 'public' . str_replace('/', DIRECTORY_SEPARATOR, $uploadDir);

        return $this->file->move($destDir, $fileName);
    }

    /**
     * 腾讯云上传
     * @param $saveName
     * @return bool
     */
    public function cosUpload($saveName = null): bool
    {
        $saveName = $saveName ?: $this->getSaveName(); // 保存文件名
        $saveName = '/' . ltrim($saveName, '/');
        $msg      = ''; // 错误信息
        $class    = new QcloudCos();
        $res      = $class->uploadFile($this->file, $saveName, $msg);
        if ($res === false) {
            throw new FileException($msg);
        }
        return true;
    }

    /**
     * 阿里云上传
     * @param $saveName
     * @return bool
     */
    public function ossUpload($saveName = null): bool
    {
        $saveName = $saveName ?: $this->getSaveName(); // 保存文件名
        $saveName = ltrim($saveName, '/');
        $msg      = ''; // 错误信息
        $class    = new QcloudOss();
        $res      = $class->uploadFile($this->file, $saveName, $msg);
        if ($res === false) {
            throw new FileException($msg);
        }
        return true;
    }

}
